using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ServiceStack.VirtualPath;

namespace ServiceStack.IO;

public class MultiVirtualFiles 
    : AbstractVirtualPathProviderBase, IVirtualFiles
{
    public List<IVirtualPathProvider> ChildProviders { get; set; }

    public override IVirtualDirectory RootDirectory => ChildProviders.FirstOrDefault().RootDirectory;

    public override string VirtualPathSeparator => "/";
    public override string RealPathSeparator => Convert.ToString(Path.DirectorySeparatorChar);

    public MultiVirtualFiles(params IVirtualPathProvider[] childProviders) 
    {
        if (childProviders == null || childProviders.Length == 0)
            throw new ArgumentNullException(nameof(childProviders));

        this.ChildProviders = new List<IVirtualPathProvider>(childProviders);
        Initialize();
    }

    protected sealed override void Initialize() {}

    public override string CombineVirtualPath(string basePath, string relativePath)
    {
        return basePath.CombineWith(relativePath);
    }

    public override bool DirectoryExists(string virtualPath)
    {
        var hasDirectory = ChildProviders.Any(childProvider => childProvider.DirectoryExists(virtualPath));
        return hasDirectory;
    }

    public override bool FileExists(string virtualPath)
    {
        var hasFile = ChildProviders.Any(childProvider => childProvider.FileExists(virtualPath));
        return hasFile;
    }

    public override IVirtualFile GetFile(string virtualPath)
    {
        return ChildProviders.Select(childProvider => childProvider.GetFile(virtualPath))
            .FirstOrDefault(file => file != null);
    }

    public override IVirtualDirectory GetDirectory(string virtualPath) => 
        MultiVirtualDirectory.ToVirtualDirectory(ChildProviders.Select(p => p.GetDirectory(virtualPath)).Where(dir => dir != null));

    public override IEnumerable<IVirtualFile> GetAllMatchingFiles(string globPattern, int maxDepth = int.MaxValue)
    {
        return ChildProviders.SelectMany(p => p.GetAllMatchingFiles(globPattern, maxDepth))
            .Distinct();
    }

    public override IEnumerable<IVirtualFile> GetAllFiles()
    {
        return ChildProviders.SelectMany(x => x.GetAllFiles())
            .Distinct();
    }
        
    public override IEnumerable<IVirtualFile> GetRootFiles()
    {
        return ChildProviders.SelectMany(x => x.GetRootFiles());
    }

    public override IEnumerable<IVirtualDirectory> GetRootDirectories()
    {
        return ChildProviders.SelectMany(x => x.GetRootDirectories());
    }

    public override bool IsSharedFile(IVirtualFile virtualFile)
    {
        return virtualFile.VirtualPathProvider.IsSharedFile(virtualFile);
    }

    public override bool IsViewFile(IVirtualFile virtualFile)
    {
        return virtualFile.VirtualPathProvider.IsViewFile(virtualFile);
    }

    public override string ToString()
    {
        var sb = new List<string>();
        ChildProviders.Each(x => sb.Add(x.ToString()));
        return string.Join(", ", sb.ToArray());
    }

    public IEnumerable<IVirtualPathProvider> ChildVirtualFiles
    {
        get { return ChildProviders.Where(x => x is IVirtualFiles); }
    }

    public void WriteFile(string filePath, string textContents)
    {
        ChildVirtualFiles.Each(x => x.WriteFile(filePath, textContents));
    }

    public void WriteFile(string filePath, Stream stream)
    {
        ChildVirtualFiles.Each(x => x.WriteFile(filePath, stream));
    }

    public void WriteFiles(IEnumerable<IVirtualFile> files, Func<IVirtualFile, string> toPath = null)
    {
        ChildVirtualFiles.Each(x => x.WriteFiles(files, toPath));
    }

    public void AppendFile(string filePath, string textContents)
    {
        ChildVirtualFiles.Each(x => x.AppendFile(filePath, textContents));
    }

    public void AppendFile(string filePath, Stream stream)
    {
        ChildVirtualFiles.Each(x => x.AppendFile(filePath, stream));
    }

    public void DeleteFile(string filePath)
    {
        ChildVirtualFiles.Each(x => x.DeleteFile(filePath));
    }

    public void DeleteFiles(IEnumerable<string> filePaths)
    {
        ChildVirtualFiles.Each(x => x.DeleteFiles(filePaths));
    }

    public void DeleteFolder(string dirPath)
    {
        ChildVirtualFiles.Each(x => x.DeleteFolder(dirPath));
    }
}

public class MultiVirtualDirectory : IVirtualDirectory
{
    public static IVirtualDirectory ToVirtualDirectory(IEnumerable<IVirtualDirectory> dirs)
    {
        var arr = dirs.ToArray();
        return arr.Length == 0
            ? null
            : arr.Length == 1
                ? arr[0]
                : new MultiVirtualDirectory(arr);
    }

    private readonly IVirtualDirectory[] dirs;

    public MultiVirtualDirectory(IVirtualDirectory[] dirs)
    {
        if (dirs.Length == 0)
            throw new ArgumentNullException(nameof(dirs));

        this.dirs = dirs;
    }

    public IVirtualDirectory Directory => this;
    public string Name => this.First().Name;
    public string VirtualPath => this.First().VirtualPath;
    public string RealPath => this.First().RealPath;
    public bool IsDirectory => true;
    public DateTime LastModified => this.First().LastModified;

    public IEnumerator<IVirtualNode> GetEnumerator() => dirs.SelectMany(dir => dir).GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => dirs.SelectMany(dir => dir).GetEnumerator();

    public bool IsRoot => this.dirs.First().IsRoot;

    public IVirtualDirectory ParentDirectory =>
        ToVirtualDirectory(dirs.SelectMany(x => x.ParentDirectory).Where(x => x != null).Cast<IVirtualDirectory>());

    public IEnumerable<IVirtualFile> Files => dirs.SelectMany(x => x.Files);

    public IEnumerable<IVirtualDirectory> Directories => dirs.SelectMany(x => x.Directories);

    public IVirtualFile GetFile(string virtualPath)
    {
        foreach (var dir in dirs)
        {
            var file = dir.GetFile(virtualPath);
            if (file != null)
                return file;
        }
        return null;
    }

    public IVirtualFile GetFile(Stack<string> virtualPath)
    {
        foreach (var dir in dirs)
        {
            var file = dir.GetFile(virtualPath);
            if (file != null)
                return file;
        }
        return null;
    }

    public IVirtualDirectory GetDirectory(string virtualPath)
    {
        foreach (var dir in dirs)
        {
            var sub = dir.GetDirectory(virtualPath);
            if (sub != null)
                return sub;
        }
        return null;
    }

    public IVirtualDirectory GetDirectory(Stack<string> virtualPath)
    {
        foreach (var dir in dirs)
        {
            var sub = dir.GetDirectory(virtualPath);
            if (sub != null)
                return sub;
        }
        return null;
    }

    public IEnumerable<IVirtualFile> GetAllMatchingFiles(string globPattern, int maxDepth = Int32.MaxValue)
    {
        foreach (var dir in dirs)
        {
            var files = dir.GetAllMatchingFiles(globPattern, maxDepth);
            foreach (var file in files)
            {
                yield return file;
            }
        }
    }
}